package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.search.service.repository.AlbumIndexRepository;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@RefreshScope
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private CategoryFeignClient categoryFeignClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private AlbumIndexRepository albumIndexRepository;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${ranking.dimension.array}")
    private String rankingDimensionArray;

    @Override
    @Async
    public void upperAlbum(Long albumId) {
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            Assert.notNull(albumInfoResult, "查询专辑信息失败。");
            AlbumInfo albumInfo = albumInfoResult.getData();
            Assert.notNull(albumInfo, "查询专辑信息失败。");
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);

            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (!CollectionUtils.isEmpty(albumAttributeValueVoList)){
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValue -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                    attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        },threadPoolExecutor).exceptionally(throwable -> {
            log.error("查询专辑信息失败：{}",throwable.getMessage());
            return null;
        });

        CompletableFuture<Integer> cateCompletableFuture = albumInfoCompletableFuture.thenApplyAsync(albumInfo -> {
            Result<BaseCategoryView> categoryViewResult = categoryFeignClient.getBaseCategoryView(albumInfo.getCategory3Id());
            Assert.notNull(categoryViewResult, "查询分类数据失败。");
            BaseCategoryView baseCategoryView = categoryViewResult.getData();
            Assert.notNull(baseCategoryView, "查询分类数据失败。");

            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
            return 1;
        },threadPoolExecutor).exceptionally(throwable -> {
            log.error("查询分类数据失败：{}",throwable.getMessage());
            return 0;
        });

        CompletableFuture<Integer> userCompletableFuture = albumInfoCompletableFuture.thenApplyAsync(albumInfo -> {
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(albumInfo.getUserId());
            Assert.notNull(userInfoVoResult, "查询作者信息失败。");
            UserInfoVo userInfoVo = userInfoVoResult.getData();
            Assert.notNull(userInfoVo, "查询作者信息失败。");
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
            return 1;
        },threadPoolExecutor).exceptionally(throwable -> {
            log.error("查询作者信息失败：{}",throwable.getMessage());
            return 0;
        });

        albumInfoIndex.setPlayStatNum(new Random().nextInt(10000));
        albumInfoIndex.setSubscribeStatNum(new Random().nextInt(10000));
        albumInfoIndex.setBuyStatNum(new Random().nextInt(10000));
        albumInfoIndex.setCommentStatNum(new Random().nextInt(10000));
        albumInfoIndex.setHotScore(Double.valueOf(new Random().nextInt(10000)));

        try {
            if (null == albumInfoCompletableFuture.get() || 0 == cateCompletableFuture.get() || 0 == userCompletableFuture.get()){
                throw new GuiguException(20001,"远程调数据失败");
            }
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
        CompletableFuture.allOf(albumInfoCompletableFuture,cateCompletableFuture,userCompletableFuture).join();
        albumIndexRepository.save(albumInfoIndex);

        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(UUID.randomUUID().toString().replace("-",""));
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));
        this.suggestIndexRepository.save(suggestIndex);

        SuggestIndex albumIntroSuggestIndex = new SuggestIndex();
        albumIntroSuggestIndex.setId(UUID.randomUUID().toString().replaceAll("-",""));
        albumIntroSuggestIndex.setTitle(albumInfoIndex.getAlbumIntro());
        albumIntroSuggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumIntro()}));
        albumIntroSuggestIndex.setKeywordPinyin(new Completion(new String[] {PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumIntro())}));
        albumIntroSuggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumIntro())}));
        this.suggestIndexRepository.save(albumIntroSuggestIndex);

        SuggestIndex announcerSuggestIndex = new SuggestIndex();
        announcerSuggestIndex.setId(UUID.randomUUID().toString().replaceAll("-",""));
        announcerSuggestIndex.setTitle(albumInfoIndex.getAnnouncerName());
        announcerSuggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAnnouncerName()}));
        announcerSuggestIndex.setKeywordPinyin(new Completion(new String[] {PinYinUtils.toHanyuPinyin(albumInfoIndex.getAnnouncerName())}));
        announcerSuggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAnnouncerName())}));
        this.suggestIndexRepository.save(announcerSuggestIndex);
    }

    @Override
    @Async
    public void lowerAlbum(Long albumId) {
        albumIndexRepository.deleteById(albumId);
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        SearchRequest searchRequest = this.queryBuildDsl(albumIndexQuery);
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        AlbumSearchResponseVo albumSearchResponseVo = this.parseResultDate(searchResponse);
        albumSearchResponseVo.setTotal(searchResponse.hits().total().value());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        Long totalPages = (albumSearchResponseVo.getTotal() + albumIndexQuery.getPageSize()-1)/albumIndexQuery.getPageSize();
        albumSearchResponseVo.setTotalPages(totalPages);
        return albumSearchResponseVo;
    }

    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        Result<List<BaseCategory3>> baseCategory3ListResult = categoryFeignClient.findTopBaseCategory3(category1Id);
        List<BaseCategory3> baseCategory3List = baseCategory3ListResult.getData();
        Map<Long,BaseCategory3> category3IdToMap = baseCategory3List.stream()
                .collect(Collectors.toMap(BaseCategory3::getId,baseCategory3 -> baseCategory3));
        List<Long> idList = baseCategory3List.stream()
                .map(BaseCategory3::getId).collect(Collectors.toList());
        List<FieldValue> valueList = idList.stream().map(id -> FieldValue.of(id)).collect(Collectors.toList());

        SearchRequest.Builder request = new SearchRequest.Builder();
        request.index("albuminfo").query(q->q.terms(f->f.field("category3Id")
                .terms(new TermsQueryField.Builder().value(valueList).build())));
        request.aggregations("groupByCategory3IdAgg",a->a.terms(t->t.field("category3Id"))
                .aggregations("topTenHotScoreAgg",a1->a1.topHits(s->s.size(6).sort(sort->sort.field(f->f.field("hotScore").order(SortOrder.Desc))))));

        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(request.build(), AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<Map<String,Object>> result = new ArrayList<>();
        Aggregate groupByCategory3IdAgg = searchResponse.aggregations().get("groupByCategory3IdAgg");
        groupByCategory3IdAgg.lterms().buckets().array().forEach(item->{
            List<AlbumInfoIndex> albumInfoIndexList = new ArrayList<>();
            long category3Id = item.key();
            Aggregate topTenHotScoreAgg = item.aggregations().get("topTenHotScoreAgg");
            topTenHotScoreAgg.topHits().hits().hits().forEach(
                    hit ->{
                        String json = hit.source().toString();
                        AlbumInfoIndex albumInfoIndex = JSON.parseObject(json,AlbumInfoIndex.class);
                        albumInfoIndexList.add(albumInfoIndex);
                    });
            Map<String,Object> map = new HashMap<>();
            map.put("baseCategory3",category3IdToMap.get(category3Id));
            map.put("list",albumInfoIndexList);
            result.add(map);
        });
        return result;
    }

    @Override
    public List<String> completeSuggest(String keyword) {
        SearchRequest.Builder searchRequest = new SearchRequest.Builder();
        searchRequest.index("suggestinfo").suggest(
                s->s.suggesters("suggestionKeyword",f->f.prefix(keyword).completion(
                                c->c.field("keyword").skipDuplicates(true).size(10)
                                        .fuzzy(z->z.fuzziness("auto"))
                        ))
                        .suggesters("suggestionkeywordPinyin",f->f.prefix(keyword).completion(
                                c->c.field("keywordPinyin").skipDuplicates(true).size(10)
                                        .fuzzy(z->z.fuzziness("auto"))
                        ))
                        .suggesters("suggestionkeywordSequence",f->f.prefix(keyword).completion(
                                c->c.field("keywordSequence").skipDuplicates(true).size(10)
                                        .fuzzy(z->z.fuzziness("auto"))
                        ))
        );

        SearchResponse<SuggestIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest.build(),SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        HashSet<String> titleSet = new HashSet<>();
        titleSet.addAll(this.parseResultDate(searchResponse,"suggestionkeywordSequence"));
        titleSet.addAll(this.parseResultDate(searchResponse,"suggestionkeywordPinyin"));
        titleSet.addAll(this.parseResultDate(searchResponse,"suggestionKeyword"));

        return new ArrayList<>(titleSet);
    }

    @Override
    public void updateLatelyAlbumRanking() {
        Result<List<BaseCategory1>> baseCategory1Result = categoryFeignClient.findAllCategory1();
        Assert.notNull(baseCategory1Result,"一级分类结果集为空！");
        List<BaseCategory1> baseCategory1List = baseCategory1Result.getData();
        Assert.notNull(baseCategory1List,"一级分类集合为空！");

        String[] split = rankingDimensionArray.split(",");
        for (BaseCategory1 baseCategory1 : baseCategory1List) {
            Long category1Id = baseCategory1.getId();
            for (String ranking : split) {
                SearchResponse<AlbumInfoIndex> searchResponse = null;
                try {
                    // 修复：添加.size(10)限制返回数量
                    searchResponse = elasticsearchClient.search(
                            s -> s.index("albuminfo")
                                    .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                    .sort(s1->s1.field(f->f.field(ranking).order(SortOrder.Desc)))
                                    .size(10),  // 只取前10条排行榜数据
                            AlbumInfoIndex.class);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                List<AlbumInfoIndex> albumInfoIndexList = searchResponse.hits().hits().stream()
                        .map(Hit::source)
                        .collect(Collectors.toList());
//                把查到的排行榜数据存入redis：
                redisTemplate.opsForHash().put(RedisConstant.RANKING_KEY_PREFIX+category1Id, ranking, albumInfoIndexList);
            }
        }
    }

    @Override
    public List<AlbumInfoIndex> findRankingList(Long category1Id, String ranking) {
       return (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(RedisConstant.RANKING_KEY_PREFIX+category1Id,ranking);



    }

    private List<String> parseResultDate(SearchResponse<SuggestIndex> response,String suggestName){
        List<String> suggestList = new ArrayList<>();
        Map<String, List<Suggestion<SuggestIndex>>> groupBySuggestionListAggMap = response.suggest();
        if (groupBySuggestionListAggMap.containsKey(suggestName)) {
            groupBySuggestionListAggMap.get(suggestName).forEach(item->{
                CompletionSuggest<SuggestIndex> completionSuggest = item.completion();
                completionSuggest.options().forEach(it->{
                    SuggestIndex suggestIndex = it.source();
                    suggestList.add(suggestIndex.getTitle());
                });
            });
        }
        return suggestList;
    }

    private AlbumSearchResponseVo parseResultDate(SearchResponse<AlbumInfoIndex> searchResponse) {
        AlbumSearchResponseVo searchResponseVo = new AlbumSearchResponseVo();
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        searchResponseVo.setTotal(hits.total().value());

        List<Hit<AlbumInfoIndex>> subHits = hits.hits();
        if (!CollectionUtils.isEmpty(subHits)){
            List<AlbumInfoIndexVo> list = subHits.stream().map(albumInfoIndexHit -> {
                AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
                AlbumInfoIndex albumInfoIndex = albumInfoIndexHit.source();
                BeanUtils.copyProperties(albumInfoIndex,albumInfoIndexVo);

                if(null!=albumInfoIndexHit.highlight() && null!=albumInfoIndexHit.highlight().get("albumTitle")){
                    String albumTitle = albumInfoIndexHit.highlight().get("albumTitle").get(0);
                    albumInfoIndexVo.setAlbumTitle(albumTitle);
                }
                return albumInfoIndexVo;
            }).collect(Collectors.toList());
            searchResponseVo.setList(list);
        }
        return searchResponseVo;
    }

    private SearchRequest queryBuildDsl(AlbumIndexQuery albumIndexQuery) {
        String keyword = albumIndexQuery.getKeyword();
        SearchRequest.Builder requestBuilder = new SearchRequest.Builder();
        BoolQuery.Builder boolQuery = new BoolQuery.Builder();

        if (!StringUtils.isEmpty(keyword)){
            boolQuery.should(f->f.match(s->s.field("albumTitle").query(keyword)));
            boolQuery.should(f->f.match(s->s.field("albumInfo").query(keyword)));
            requestBuilder.highlight(h->h.fields("albumTitle",f->f.preTags("<span style=color:red>").postTags("</span>")));
        }

        Long category1Id = albumIndexQuery.getCategory1Id();
        if (!StringUtils.isEmpty(category1Id)){
            boolQuery.filter(f->f.term(s->s.field("category1Id").value(category1Id)));
        }

        Long category2Id = albumIndexQuery.getCategory2Id();
        if (!StringUtils.isEmpty(category2Id)){
            boolQuery.filter(f->f.term(s->s.field("category2Id").value(category2Id)));
        }

        Long category3Id = albumIndexQuery.getCategory3Id();
        if (!StringUtils.isEmpty(category3Id)){
            boolQuery.filter(f->f.term(s->s.field("category3Id").value(category3Id)));
        }

        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)){
            for (String attribute : attributeList) {
                String[] split = attribute.split(":");
                if (null != split && split.length == 2){
                    NestedQuery nestedQuery = NestedQuery.of(f->f.path("attributeValueIndexList")
                            .query(q->q.bool(m->m.must(s->s.match(a->a.field("attributeValueIndexList.attributeId").query(split[0])))
                                    .must(s->s.match(a->a.field("attributeValueIndexList.valueId").query(split[1])))
                            )));
                    boolQuery.filter(f->f.nested(nestedQuery));
                }
            }
        }

        String order = albumIndexQuery.getOrder();
        String orderField = "";
        String sort = "";
        if (!StringUtils.isEmpty(order)){
            String[] split = order.split(":");
            if (null!=split&&split.length==2){
                switch (split[0]){
                    case "1":
                        orderField = "hotScore";
                        break;
                    case "2":
                        orderField = "playStatNum";
                        break;
                    case "3":
                        orderField = "createTime";
                        break;
                }
                sort = split[1];
            }
            // 修复：排序方向判断错误
            String finalSort = sort;
            String finalOrderField = orderField;
            if (!StringUtils.isEmpty(finalOrderField)) {
                requestBuilder.sort(f -> f.field(o -> o.field(finalOrderField)
                        .order("asc".equals(finalSort) ? SortOrder.Asc : SortOrder.Desc)));
            }
        } else {
            requestBuilder.sort(f->f.field(o->o.field("_score").order(SortOrder.Desc)));
        }

        requestBuilder.source(s->s.filter(f->f.excludes("attributeValueIndexList")));
        Integer from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        requestBuilder.from(from);
        requestBuilder.size(albumIndexQuery.getPageSize());

        requestBuilder.index("albuminfo").query(f->f.bool(boolQuery.build()));
        System.out.println("dsl:\t"+requestBuilder.build().toString());
        return requestBuilder.build();
    }
}
